# -*- coding: utf-8 -*-
# @file    : device.py
# @Date    : 2020/10/21
# @Author  :
# @Version : 1.0.0

import re
import sys
import abc
import functools
import asyncio

from pynput import mouse as _mouse
from pynput import keyboard as _keyboard

from .util import get_clipboard_text
from .exception import StopGroup, SkipAction


class _DeviceMeta(abc.ABCMeta):
    def __init__(cls, class_name, class_parents, class_attr):
        super(_DeviceMeta, cls).__init__(class_name, class_parents, class_attr)
        if class_name != 'Device':
            # 记录Device的子类
            Device.supported_device[class_name.lower()] = cls


class Device(metaclass=_DeviceMeta):
    supported_device = {}

    def __init__(self):
        # operation records
        self.history = []

    @classmethod
    def action(cls, func):
        func._action = True

        @functools.wraps(func)
        def wrap(self, *args):
            result = func(self, *args)
            self.history.append((func.__name__, *args))
            return result

        return wrap

    @abc.abstractmethod
    def __repr__(self):
        pass


DEV = Device


class System(Device):
    """
    Syntax:
        {system[sleep::1000]}
        {system[print::msg]}
    """

    @DEV.action
    async def sleep(self, expr: str):
        ms = int(expr)
        await asyncio.sleep(ms / 1000)

    @DEV.action
    def print(self, expr: str):
        print(expr)

    @DEV.action
    def quit(self, expr: str):
        print('退出程序, bye！！')
        sys.exit(0)

    @DEV.action
    def stop_with(self, expr: str):
        text = get_clipboard_text()
        if not expr:
            return

        result = re.search(expr, text, re.DOTALL)
        if result:
            print("满足匹配规则: %s" % result.group())
            raise StopGroup("条件中断")

    @DEV.action
    def skip_with(self, expr: str):
        text = get_clipboard_text()
        if not expr:
            return

        result = re.search(expr, text, re.DOTALL)
        if result:
            print("满足匹配规则: %s" % result.group())
            raise SkipAction("条件跳过")

    @DEV.action
    def skip_step(self, expr: str):
        num = int(expr)
        assert num > 0, "跳过步数应该大于0"
        raise SkipAction(num=num)

    def __repr__(self):
        return "System(id=%s)" % id(self)


class Mouse(Device):
    """
    key define:
        left    左键
        right   右键
        middle  中键

    Action define:
        press           按下
        release         释放
        click           单击 (按下->释放)
        double_click    双击 (按下->释放)*2
        move            相对位移, 基于当前鼠标位置相对移动
        move_to         绝对位移, 移动到目标坐标
        move_back       回退位移, 回到上次鼠标的位置

    Syntax:
        {mouse[press::left+right]}
        {mouse[click::middle]}
        {mouse[move::1,2]}
    """
    _ctrl = _mouse.Controller()

    class Buttons(object):

        __slots__ = ('keys',)

        _btn_table = {
            'left': _mouse.Button.left,
            'right': _mouse.Button.right,
            'middle': _mouse.Button.middle
        }

        def __init__(self, *keys):
            self.keys = keys

        @classmethod
        def from_expr(cls, expr: str):
            keys = cls.parse(expr)
            return cls(*keys)

        @classmethod
        def parse(cls, expr: str):
            parts = expr.replace(" ", "").lower().split('+')
            if not parts:
                # null error
                raise ValueError(parts)
            keys = []
            for key_str in parts:
                if key_str not in cls._btn_table:
                    raise ValueError(key_str)
                keys.append(cls._btn_table[key_str])

            return tuple(keys)

        def __repr__(self):
            return "Mouse.Buttons(%s)" % (self.keys,)

    class Point(object):

        __slots__ = ('x', 'y')

        def __init__(self, x, y):
            self.x = x
            self.y = y

        @classmethod
        def from_expr(cls, expr: str):
            x, y = cls.parse(expr)
            return cls(x, y)

        @classmethod
        def parse(cls, expr: str):
            parts = expr.replace(" ", "").split(',')
            if len(parts) != 2:
                raise ValueError(expr)
            try:
                x = int(parts[0])
                y = int(parts[1])
            except (KeyError, ValueError):
                raise ValueError(expr)
            return x, y

        @property
        def tuple(self):
            return self.x, self.y

        def dis_x(self, point):
            return self.x - point.x

        def dis_y(self, point):
            return self.y - point.y

        def __sub__(self, point):
            return self.x - point.x, self.y - point.y

        def __add__(self, point):
            return self.x + point.x, self.y + point.y

        def __repr__(self):
            return "Mouse.Point(%s, %s)" % (self.x, self.y)

    def __init__(self):
        super().__init__()
        self.__point_queue = [self.position]
        self.last_point = self.position

    def __record_point(self, point):
        self.__point_queue.append(point)
        self.last_point = self.__point_queue[-2:][0]

    @DEV.action
    def move(self, expr: str):
        d_point = self.Point.from_expr(expr)
        current = self.position
        x, y = current + d_point
        self._ctrl.position = x, y
        self.__record_point(current)

    @DEV.action
    def move_to(self, expr: str):
        point = self.Point.from_expr(expr)
        self._ctrl.position = point.tuple
        self.__record_point(point)

    @DEV.action
    def move_back(self, expr: str = None):
        # TODO 使用expr的值来决定后退的步数
        self._ctrl.position = self.last_point.tuple
        if len(self.__point_queue) > 1:
            self.__point_queue.pop(-1)
            self.last_point = self.__point_queue[-2:][0]

    @DEV.action
    def click(self, expr: str):
        buttons = self.Buttons.from_expr(expr)
        for key in buttons.keys:
            self._ctrl.click(key)

    @DEV.action
    def double_click(self, expr: str):
        buttons = self.Buttons.from_expr(expr)
        for key in buttons.keys:
            self._ctrl.click(key, 2)

    @DEV.action
    def press(self, expr: str):
        buttons = self.Buttons.from_expr(expr)
        for key in buttons.keys:
            self._ctrl.press(key)

    @DEV.action
    def release(self, expr: str):
        buttons = self.Buttons.from_expr(expr)
        for key in reversed(buttons.keys):
            self._ctrl.release(key)

    @DEV.action
    def display_position(self, expr: str):
        print(self.position)

    @property
    def position(self):
        return self.Point(*self._ctrl.position)

    def __repr__(self):
        return "Mouse(id=%s)" % id(self)


class Keyboard(Device):
    """
    Action define:
        press       按下
        release     释放
        type        键入

    Syntax:
        {keyboard[press::a]}
        {keyboard[press::b+c]}
        {keyboard[press::<ctrl>+a]}
        {keyboard[release::a]}
        {keyboard[type::a]}          按下a后， 释放a
        {keyboard[type::ab]}         按下a后， 释放a, 再按下b, 释放b
        {keyboard[type::中文]}        对于中文, 看作输入效果
    """

    _ctrl = _keyboard.Controller()

    class Keys(object):

        __slots__ = ('keys',)

        def __init__(self, *keys):
            self.keys = keys

        @classmethod
        def from_expr(cls, expr: str):
            keys = cls.parse(expr)
            return cls(*keys)

        @classmethod
        def parse(cls, expr: str):
            expr = expr.replace(" ", "")
            keys = tuple(_keyboard.HotKey.parse(expr))
            result = []
            for key in keys:
                if isinstance(key, _keyboard.KeyCode):
                    result.append(key)
                else:
                    result.insert(0, key)
            return result

        def __repr__(self):
            return "Keyboard.Keys(keys=%s)" % self.keys

    class Item(object):
        _re_rarity = re.compile(r"稀 有 度: ([\w]+)")
        _re_search_name = re.compile(r'([\w 【】]+)')
        _search_name = {
            '稀有': lambda names: names[1] if len(names) > 1 else names[0]
        }

        def __init__(self, **kwargs):
            rarity = kwargs.get('rarity')
            names = kwargs.get('names')
            assert names, "物品无有效名称"
            self.rarity = rarity
            self.names = names
            self.search_name = self.get_search_name(rarity, names)

        @classmethod
        def from_expr(cls, expr: str):
            info = cls.parse(expr)
            if not info or not info['names']:
                return None
            return cls(**info)

        @classmethod
        def get_search_name(cls, rarity, names):
            func = cls._search_name.get(rarity, lambda names: names[0])
            name = func(names)
            name = cls._re_search_name.search(name)
            name = name.group(1) if name else name
            return name

        @classmethod
        def parse(cls, expr: str):
            expr = expr.replace("\r\n", '\n').replace('\r', '\n')
            expr = expr.split('--------')[0].strip('\n')
            lines = expr.split("\n")
            if len(lines) < 2:
                return {}
            rarity = cls._re_rarity.match(lines[1])
            if not rarity:
                return {}
            rarity = rarity.group(1)
            names = lines[2:]
            return {
                'rarity': rarity,
                'names': names
            }

        def __repr__(self):
            return "Keyboard.Item(search_name=%s, rarity=%s, names=%s)" % (self.search_name, self.rarity, self.names)

    @DEV.action
    def press(self, expr: str):
        keys = self.Keys.from_expr(expr)
        for key in keys.keys:
            self._ctrl.press(key)

    @DEV.action
    def release(self, expr: str):
        keys = self.Keys.from_expr(expr)
        for key in reversed(keys.keys):
            self._ctrl.release(key)

    @DEV.action
    def type(self, expr: str):
        self._ctrl.type(expr)

    @DEV.action
    def paste_item(self, expr: str):
        text = get_clipboard_text()
        item = self.Item.from_expr(text)
        if not item:
            print("剪贴板无有效物品信息： \n%s" % text)
            return
        self._ctrl.type(item.search_name)

    def __repr__(self):
        return "Keyboard(id=%s)" % id(self)
